package com.igoal.utils;

import java.awt.AlphaComposite;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.Graphics2D;
import java.awt.GraphicsConfiguration;
import java.awt.Image;
import java.awt.MediaTracker;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.Toolkit;
import java.awt.Transparency;
import java.awt.geom.AffineTransform;
import java.awt.image.BufferedImage;
import java.awt.image.BufferedImageOp;
import java.awt.image.ConvolveOp;
import java.awt.image.CropImageFilter;
import java.awt.image.FilteredImageSource;
import java.awt.image.Kernel;
import java.awt.image.RasterFormatException;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.HashMap;
import java.util.Map;

import javax.imageio.ImageIO;
import javax.swing.ImageIcon;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import sun.awt.image.BufferedImageGraphicsConfig;

import com.igoal.web.Constants;
import com.sun.image.codec.jpeg.JPEGCodec;
import com.sun.image.codec.jpeg.JPEGEncodeParam;
import com.sun.image.codec.jpeg.JPEGImageEncoder;

@SuppressWarnings("restriction")
public class ImageUtils {

	private static int IMAGE_QUALITY = 100; // 0 <= quality <= 100
	private static Rectangle clip;
	private static Log logger = LogFactory.getLog(ImageUtils.class);
	private static String DEFAULT_AVATAR_EXT = "png";

	public static String scaleEntryImage(String inputPath, String outputPath)
			throws Exception {
		int inputLastDotPos = inputPath.lastIndexOf('.');
		int outputLastDotPos = outputPath.lastIndexOf('.');
		if (inputLastDotPos > 0 && outputLastDotPos > 0) {
			String ext = inputPath.substring(inputLastDotPos + 1);
			String inputImagePathWithoutExt = inputPath.substring(0,
					inputLastDotPos);
			String outputImagePathWithoutExt = outputPath.substring(0,
					outputLastDotPos);
			String defaultType = DEFAULT_AVATAR_EXT;
			if ("bmp".equals(ext)) {
				convertToImageType(inputImagePathWithoutExt, ext, defaultType);
				inputPath = inputImagePathWithoutExt + "." + defaultType;
				outputPath = outputImagePathWithoutExt + "." + defaultType;
			} else if ("gif".equals(ext)) {
				convertToImageType(inputImagePathWithoutExt, ext, defaultType);
				inputPath = inputImagePathWithoutExt + "." + defaultType;
				outputPath = outputImagePathWithoutExt + "." + defaultType;
			}
		}

		// TODO: change ENTRY_IMAGE_WIDTH value to web.xml or config file
		scaleImageWidth(inputPath, outputPath, Constants.ENTRY_IMAGE_WIDTH);

		return outputPath;
	}

	public static String scaleXmasImage(String inputPath, String outputPath,
			boolean scaleWidth) throws Exception {
		int inputLastDotPos = inputPath.lastIndexOf('.');
		int outputLastDotPos = outputPath.lastIndexOf('.');
		if (inputLastDotPos > 0 && outputLastDotPos > 0) {
			String ext = inputPath.substring(inputLastDotPos + 1);
			String inputImagePathWithoutExt = inputPath.substring(0,
					inputLastDotPos);
			String outputImagePathWithoutExt = outputPath.substring(0,
					outputLastDotPos);
			String defaultType = DEFAULT_AVATAR_EXT;
			if ("bmp".equals(ext)) {
				convertToImageType(inputImagePathWithoutExt, ext, defaultType);
				inputPath = inputImagePathWithoutExt + "." + defaultType;
				outputPath = outputImagePathWithoutExt + "." + defaultType;
			} else if ("gif".equals(ext)) {
				convertToImageType(inputImagePathWithoutExt, ext, defaultType);
				inputPath = inputImagePathWithoutExt + "." + defaultType;
				outputPath = outputImagePathWithoutExt + "." + defaultType;
			}
		}

		// TODO: change ENTRY_IMAGE_WIDTH value to web.xml or config file
		if (scaleWidth) {
			scaleImageWidth(inputPath, outputPath, Constants.IMAGE_CARD_WIDTH);
		} else {
			scaleImageHeight(inputPath, outputPath, Constants.IMAGE_CARD_HEIGHT);
		}

		return outputPath;
	}

	public static String scaleAvatarImage(String inputPath, String outputPath,
			int scaleWidth) throws Exception {
		logger.info("Scaling '" + inputPath + "' to '" + outputPath + "'");
		int inputLastDotPos = inputPath.lastIndexOf('.');
		int outputLastDotPos = outputPath.lastIndexOf('.');
		if (inputLastDotPos > 0 && outputLastDotPos > 0) {
			String ext = inputPath.substring(inputLastDotPos + 1);
			String inputImagePathWithoutExt = inputPath.substring(0,
					inputLastDotPos);
			String outputImagePathWithoutExt = outputPath.substring(0,
					outputLastDotPos);
			String defaultType = DEFAULT_AVATAR_EXT;
//			if ("bmp".equals(ext)) {
//				convertToImageType(inputImagePathWithoutExt, ext, defaultType);
//				inputPath = inputImagePathWithoutExt + "." + defaultType;
//				outputPath = outputImagePathWithoutExt + "." + defaultType;
//			} else if ("gif".equals(ext)) {
//				convertToImageType(inputImagePathWithoutExt, ext, defaultType);
//				inputPath = inputImagePathWithoutExt + "." + defaultType;
//				outputPath = outputImagePathWithoutExt + "." + defaultType;
//			} else {
//				
//			}
			if(!DEFAULT_AVATAR_EXT.equals(ext)){
				convertToImageType(inputImagePathWithoutExt, ext, defaultType);
				inputPath = inputImagePathWithoutExt + "." + defaultType;
				outputPath = outputImagePathWithoutExt + "." + defaultType;
			}
			logger.info("New scaled information");
			logger.info("Scaling '" + inputPath + "' to '" + outputPath + "'");
		}

		scaleImageWidth(inputPath, outputPath, scaleWidth);
		logger.info("Scaled image successfully");
		return outputPath;
	}

	public static BufferedImage blurImage(BufferedImage image) {
		float ninth = 1.0f / 9.0f;
		float[] blurKernel = { ninth, ninth, ninth, ninth, ninth, ninth, ninth,
				ninth, ninth };

		Map map = new HashMap();

		map.put(RenderingHints.KEY_INTERPOLATION,
				RenderingHints.VALUE_INTERPOLATION_BILINEAR);

		map.put(RenderingHints.KEY_RENDERING,
				RenderingHints.VALUE_RENDER_QUALITY);

		map.put(RenderingHints.KEY_ANTIALIASING,
				RenderingHints.VALUE_ANTIALIAS_ON);

		RenderingHints hints = new RenderingHints(map);
		BufferedImageOp op = new ConvolveOp(new Kernel(3, 3, blurKernel),
				ConvolveOp.EDGE_NO_OP, hints);
		return op.filter(image, null);
	}

	private static BufferedImage createCompatibleImage(BufferedImage image) {
		GraphicsConfiguration gc = BufferedImageGraphicsConfig.getConfig(image);
		int w = image.getWidth();
		int h = image.getHeight();
		BufferedImage result = gc.createCompatibleImage(w, h,
				Transparency.TRANSLUCENT);
		Graphics2D g2 = result.createGraphics();
		g2.drawRenderedImage(image, null);
		g2.dispose();
		return result;
	}

//	private static BufferedImage resizeTrick(BufferedImage image, int width,
//			int height) {
	public static void scaleImageWidth(String inputPath, String outputPath,
				int scaleWidth) {
		try{
			if(scaleWidth > 74){
				scaleLargeImageWidth(inputPath, outputPath, scaleWidth);
				return;
			}
			BufferedImage resizedImaged = ImageIO.read(new File(inputPath)); 
			int imageWidth = resizedImaged.getWidth(null);
			int imageHeight = resizedImaged.getHeight(null);
			double imageRatio = (double) imageWidth / (double) imageHeight;
			int thumbWidth = scaleWidth;
			int thumbHeight = (int) (thumbWidth / imageRatio);			
			resizedImaged = createCompatibleImage(resizedImaged);
			resizedImaged = resize(resizedImaged, 100, 100);
			resizedImaged = blurImage(resizedImaged);
			resizedImaged = resize(resizedImaged, thumbWidth, thumbHeight);
			
			ImageIO.write(resizedImaged, DEFAULT_AVATAR_EXT, new File(outputPath));
		}catch (Exception e) {
			logger.error(e, e.getCause());
			StackTraceElement[] stes = e.getStackTrace();
			for (StackTraceElement ste : stes) {
				logger.error("Error at: " + ste.getClassName() + "#"
						+ ste.getMethodName() + " line " + ste.getLineNumber());
			}
		}	
	}

	private static BufferedImage resize(BufferedImage image, int width,
			int height) {
		int type = image.getType() == 0 ? BufferedImage.TYPE_INT_ARGB : image
				.getType();
		BufferedImage resizedImage = new BufferedImage(width, height, type);
		Graphics2D g = resizedImage.createGraphics();
		g.setComposite(AlphaComposite.Src);

		g.setRenderingHint(RenderingHints.KEY_INTERPOLATION,
				RenderingHints.VALUE_INTERPOLATION_BILINEAR);

		g.setRenderingHint(RenderingHints.KEY_RENDERING,
				RenderingHints.VALUE_RENDER_QUALITY);

		g.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
				RenderingHints.VALUE_ANTIALIAS_ON);

		g.drawImage(image, 0, 0, width, height, null);
		g.dispose();
		return resizedImage;
	}

	public static void scaleLargeImageWidth(String inputPath, String outputPath,
			int scaleWidth) {
		try {
			BufferedImage originalImage = ImageIO.read(new File(inputPath));
			int imageWidth = originalImage.getWidth(null);
			int imageHeight = originalImage.getHeight(null);
			double imageRatio = (double) imageWidth / (double) imageHeight;
			int thumbWidth = scaleWidth;
			int thumbHeight = (int) (thumbWidth / imageRatio);
			int type = originalImage.getType() == 0 ? BufferedImage.TYPE_INT_ARGB
					: originalImage.getType();
			BufferedImage resizedImage = new BufferedImage(thumbWidth,
					thumbHeight, type);
			Graphics2D g = resizedImage.createGraphics();
			g.drawImage(originalImage, 0, 0, thumbWidth, thumbHeight, null);
			g.dispose();
			g.setComposite(AlphaComposite.Src);

			g.setRenderingHint(RenderingHints.KEY_INTERPOLATION,
					RenderingHints.VALUE_INTERPOLATION_BILINEAR);
			g.setRenderingHint(RenderingHints.KEY_RENDERING,
					RenderingHints.VALUE_RENDER_QUALITY);
			g.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
					RenderingHints.VALUE_ANTIALIAS_ON);

			ImageIO.write(resizedImage, DEFAULT_AVATAR_EXT, new File(outputPath));
		} catch (Exception e) {
			logger.error(e, e.getCause());
			StackTraceElement[] stes = e.getStackTrace();
			for (StackTraceElement ste : stes) {
				logger.error("Error at: " + ste.getClassName() + "#"
						+ ste.getMethodName() + " line " + ste.getLineNumber());
			}
		}
	}


	public static void scaleImageHeight(String inputPath, String outputPath,
			int scaleHeight) throws Exception {
		Image image = Toolkit.getDefaultToolkit().getImage(inputPath);
		MediaTracker mediaTracker = new MediaTracker(new Container());
		mediaTracker.addImage(image, 0);
		mediaTracker.waitForID(0);

		int imageWidth = image.getWidth(null);
		int imageHeight = image.getHeight(null);
		double imageRatio = (double) imageWidth / (double) imageHeight;
		int thumbHeight = scaleHeight;
		int thumbWidth = (int) (thumbHeight * imageRatio);

		BufferedImage thumbImage = new BufferedImage(thumbWidth, thumbHeight,
				BufferedImage.TYPE_INT_RGB);
		Graphics2D graphics2D = thumbImage.createGraphics();
		graphics2D.setRenderingHint(RenderingHints.KEY_INTERPOLATION,
				RenderingHints.VALUE_INTERPOLATION_BILINEAR);
		graphics2D.drawImage(image, 0, 0, thumbWidth, thumbHeight, null);

		// save image as JPEG format
		BufferedOutputStream out = new BufferedOutputStream(
				new FileOutputStream(outputPath));
		JPEGImageEncoder encoder = JPEGCodec.createJPEGEncoder(out);
		JPEGEncodeParam param = encoder.getDefaultJPEGEncodeParam(thumbImage);
		param.setQuality((float) IMAGE_QUALITY / 100.0f, false);
		encoder.setJPEGEncodeParam(param);
		encoder.encode(thumbImage);
		out.close();
	}

	public static void cropImage(String inputImage, String outputImage,
			int cropHeight, int cropWidth, int cropStartX, int cropStartY) {
		String inputFileLocation = inputImage;
		String outputFileLocation = outputImage;

		if (logger.isDebugEnabled()) {
			logger.debug("Reading Original File : " + inputFileLocation);
		}

		BufferedImage originalImage = readImage(inputFileLocation);

		BufferedImage processedImage;
		try {
			processedImage = originalImage.getSubimage(cropStartX, cropStartY, cropWidth, cropHeight);

			if (logger.isDebugEnabled()) {
				System.out.println("Writing the cropped image to: "
						+ outputFileLocation);
			}

			writeImage(processedImage, outputFileLocation, DEFAULT_AVATAR_EXT);
			if (logger.isDebugEnabled()) {
				System.out.println("...Done");
			}
		} catch (Exception e) {
			logger.equals(e);
		}
	}
	
	public static BufferedImage cropImage(BufferedImage img, int cropWidth,
			int cropHeight, int cropStartX, int cropStartY) throws Exception {
		BufferedImage clipped = null;
		Dimension size = new Dimension(cropWidth, cropHeight);

		createClip(img, size, cropStartX, cropStartY);

		try {
			int w = clip.width;
			int h = clip.height;

			if (logger.isDebugEnabled()) {
				System.out.println("Crop Width " + w);
				System.out.println("Crop Height " + h);
				System.out.println("Crop Location " + "(" + clip.x + ","
						+ clip.y + ")");
			}

			clipped = img.getSubimage(clip.x, clip.y, w, h);

			if (logger.isDebugEnabled()) {
				System.out.println("Image Cropped. New Image Dimension: "
						+ clipped.getWidth() + "w X " + clipped.getHeight()
						+ "h");
			}
		} catch (RasterFormatException rfe) {
			logger.error("Raster format error: " + rfe.getMessage());
			return null;
		}
		return clipped;
	}

	/**
	 * This <span class="IL_AD" id="IL_AD3">method</span> crops an original
	 * image to the crop parameters provided.
	 * 
	 * If the crop rectangle lies outside the rectangle (even if partially),
	 * adjusts the rectangle to be included within the image area.
	 * 
	 * @param img
	 *            = Original Image To Be Cropped
	 * @param size
	 *            = Crop area rectangle
	 * @param clipX
	 *            = Starting X-position of crop area rectangle
	 * @param clipY
	 *            = Strating Y-position of crop area rectangle
	 * @throws Exception
	 */
	private static void createClip(BufferedImage img, Dimension size,
			int clipX, int clipY) throws Exception {
		/**
		 * Some times clip area might lie outside the original image, fully or
		 * partially. In such cases, this program will adjust the crop area to
		 * fit within the original image.
		 * 
		 * isClipAreaAdjusted flas is usded to denote if there was any
		 * adjustment made.
		 */
		boolean isClipAreaAdjusted = false;

		/** Checking for negative X Co-ordinate **/
		if (clipX < 0) {
			clipX = 0;
			isClipAreaAdjusted = true;
		}
		/** Checking for negative Y Co-ordinate **/
		if (clipY < 0) {
			clipY = 0;
			isClipAreaAdjusted = true;
		}

		/** Checking if the clip area lies outside the rectangle **/
		if ((size.width + clipX) <= img.getWidth()
				&& (size.height + clipY) <= img.getHeight()) {

			/**
			 * Setting up a clip rectangle when clip area lies within the image.
			 */

			clip = new Rectangle(size);
			clip.x = clipX;
			clip.y = clipY;
		} else {

			/**
			 * Checking if the width of the clip area lies outside the image. If
			 * so, making the image width boundary as the clip width.
			 */
			if ((size.width + clipX) > img.getWidth())
				size.width = img.getWidth() - clipX;

			/**
			 * Checking if the height of the clip area lies outside the image.
			 * If so, making the image height boundary as the clip height.
			 */
			if ((size.height + clipY) > img.getHeight())
				size.height = img.getHeight() - clipY;

			/** Setting up the clip are based on our clip area size adjustment **/
			clip = new Rectangle(size);
			clip.x = clipX;
			clip.y = clipY;

			isClipAreaAdjusted = true;

		}
		if (isClipAreaAdjusted)
			logger.error("Crop Area Lied Outside The Image."
					+ " Adjusted The Clip Rectangle\n");
	}

	/**
	 * This method reads an image from the file
	 * 
	 * @param fileLocation
	 *            -- > eg. "C:/testImage.jpg"
	 * @return BufferedImage of the file read
	 */
	public static BufferedImage readImage(String fileLocation) {
		BufferedImage img = null;
		try {
			img = ImageIO.read(new File(fileLocation));

			if (logger.isDebugEnabled()) {
				logger.debug("Image Read. Image Dimension: " + img.getWidth()
						+ "w X " + img.getHeight() + "h");
			}
		} catch (IOException e) {
			File file = new File(fileLocation);
			logger.error(e);
		}
		return img;
	}

	/**
	 * This method writes a buffered image to a file
	 * 
	 * @param img
	 *            -- > BufferedImage
	 * @param fileLocation
	 * @param <span class="IL_AD" id="IL_AD10">extension</span> --> e.g.
	 *        "jpg","gif","png"
	 */
	public static void writeImage(BufferedImage img, String fileLocation,
			String extension) {
		try {
			BufferedImage bi = img;
			File outputfile = new File(fileLocation);
			ImageIO.write(bi, extension, outputfile);
		} catch (IOException e) {
			logger.error(e);
		}
	}

	public static void convertToImageType(String inputImagePathWithoutExt,
			String fromType, String toType) {
		try {
			// Create file for the source
			File input = new File(inputImagePathWithoutExt + "." + fromType);

			if (!input.exists())
				return;

			// Read the file to a BufferedImage
			BufferedImage image;

			image = ImageIO.read(input);

			// Create a file for the output
			File output = new File(inputImagePathWithoutExt + "." + toType);

			// Write the image to the destination as a JPG
			ImageIO.write(image, toType, output);
		} catch (IOException e) {
			logger.error(e);
		}
	}

	public static void getImageSize(String path, int[] size) {
		try {
			File file = new File(path);
			if (!file.exists()) {
				return;
			}

			Image image;
			// Toolkit tool = Toolkit.getDefaultToolkit();
			// image = tool.getImage(path);
			image = new ImageIcon(path).getImage();
			int width = image.getWidth(null);
			if (width < 0) {
				width = 422;
			}
			int height = image.getHeight(null);
			size[0] = width;
			size[1] = height;

		} catch (Exception e) {
			logger.error(e);
			return;
		}
	}

	public static void copyFile(File source, File des,
			boolean deleteSourceAfterCopied) {
		try {
			InputStream in = new FileInputStream(source);
			OutputStream out = new FileOutputStream(des);

			byte[] buf = new byte[1024];
			int len;
			while ((len = in.read(buf)) > 0) {
				out.write(buf, 0, len);
			}
			in.close();
			out.close();

			if (deleteSourceAfterCopied) {
				source.delete();
			}
		} catch (Exception e) {
			logger.error(e);
		}
	}

}